using System;
using System.Drawing;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Windows.Forms;

using Pegasus.Collections.Generic;
using Pegasus.Diagnostics.Contracts;
using Pegasus.Resources;
using Pegasus.Runtime.Serialization;

namespace Pegasus.Windows.Forms
{
	/// <summary>
	/// Derives from the Form class and add functionality for application and user data, as well as saving the last position and state of the window.
	/// The data is stored in xml files. For the application data the file is in the Common Application Data folder so that all instances of the 
	/// of the application share the same data.  For the user data the file is sin the Application Data folder so that it is specific only
	/// to the current user.
	/// </summary>
	public class FormEx : Form
	{
		// Local Instance Values
		private string m_storeName = null;
		private string m_appDataFilename;
		private string m_userDataFilename;
		private DictionaryEx<string, object> m_appData;
		private DictionaryEx<string, object> m_userData;

		private Rectangle m_windowBounds;

		// Local Const Values
		private const string SETTINGS_BOUNDS = "Window.Bounds";
		private const string SETTINGS_STATE = "Window.State";

		/// <summary>
		/// Initializes a new instance of the <see cref="FormEx"/> class.
		/// </summary>
		/// <param name="storeName">Name of the store to save the data in.  This must be a valid filename with out the extension.</param>
		public FormEx( string storeName ) 
		{
			Contract.Requires( string.IsNullOrEmpty( storeName ), "Store name can not be null or empty." );

			m_storeName = storeName;

			// Use the default icon from Pegasus.
			EmbeddedResource res = new EmbeddedResource( typeof( FormEx ).Assembly, "Pegasus._LibraryResources" );
			Icon = res.GetIcon( "DialogIcon.ico" );

			LoadData();
		}

		/// <summary>
		/// Gets the application data.
		/// </summary>
		/// <value>The application data.</value>
		[Browsable( false )]
		public DictionaryEx<string, object> ApplicationData
		{
			get
			{
				return m_appData;
			}
		}

		/// <summary>
		/// Gets the application data filename.
		/// </summary>
		/// <value>The application data filename.</value>
		public string ApplicationDataFilename
		{
			get
			{
				return m_appDataFilename;
			}
		}

		/// <summary>
		/// Gets the user data.
		/// </summary>
		/// <value>The user data.</value>
		[Browsable( false )]
		public DictionaryEx<string, object> UserData
		{
			get
			{
				return m_userData;
			}
		}

		/// <summary>
		/// Gets the user data filename.
		/// </summary>
		/// <value>The user data filename.</value>
		public string UserDataFilename
		{
			get
			{
				return m_userDataFilename;
			}
		}

		/// <summary>
		/// Raises the <see cref="E:Load"/> event.
		/// </summary>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected override void OnLoad( EventArgs args )
		{
			base.OnLoad( args );
			LoadWindowSettings();
		}

		/// <summary>
		/// Raises the <see cref="E:Closed"/> event.
		/// </summary>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected override void OnClosed( EventArgs args )
		{
			base.OnClosed( args );
			SaveWindowSettings();
			SaveData();
		}

		/// <summary>
		/// Raises the <see cref="E:Resize"/> event.
		/// </summary>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected override void OnResize( EventArgs args )
		{
			base.OnResize( args );

			// Save the new bounds only if we are in normal state
			if( WindowState == FormWindowState.Normal )
			{
				m_windowBounds = Bounds;
			}
		}

		/// <summary>
		/// Raises the <see cref="E:LocationChanged"/> event.
		/// </summary>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected override void OnLocationChanged( EventArgs args )
		{
			base.OnLocationChanged( args );

			// Save the new bounds only if we are in normal state
			if( WindowState == FormWindowState.Normal )
			{
				m_windowBounds = Bounds;
			}
		}

		/// <summary>
		/// Loads the application and user data.
		/// </summary>
		protected void LoadData()
		{
			if( !string.IsNullOrEmpty( m_storeName ) )
			{
				string appFolder = Environment.GetFolderPath( Environment.SpecialFolder.CommonApplicationData );
				m_appDataFilename = Path.Combine( appFolder, m_storeName + ".xml" );

				string userFolder = Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData );
				m_userDataFilename = Path.Combine( userFolder, m_storeName + ".xml" );

				try
				{
					m_appData = (DictionaryEx<string, object>) ReadObjectFromXmlFile( m_appDataFilename );
				}
				catch( FileNotFoundException )
				{
					// ignore the file not found.
				}

				try
				{
					m_userData = (DictionaryEx<string, object>) ReadObjectFromXmlFile( m_userDataFilename );
				}
				catch( FileNotFoundException )
				{
					// ignore the file not found.
				}
			}

			// Create default Dictionaries.
			if( m_appData == null )
			{
				m_appData = new DictionaryEx<string, object>();
			}

			if( m_userData == null )
			{
				m_userData = new DictionaryEx<string, object>();
			}
		}

		/// <summary>
		/// Loads the window settings.
		/// </summary>
		private void LoadWindowSettings()
		{
			if( m_userData.ContainsKey( SETTINGS_BOUNDS ) )
			{
				Rectangle formBounds = (Rectangle) m_userData[ SETTINGS_BOUNDS ];
				Rectangle desktopBounds = Desktop.Bounds;
				
				// If the window was saved off the current desktop then center the window
				// onto the primary display
				if( !desktopBounds.IntersectsWith( formBounds ) )
				{
					Rectangle primaryScreenBounds = Desktop.PrimaryDisplay.Bounds;
					if( formBounds.Width > primaryScreenBounds.Width )
					{
						formBounds.Width = primaryScreenBounds.Width;
					}

					if( formBounds.Height > primaryScreenBounds.Height )
					{
						formBounds.Height = primaryScreenBounds.Height;
					}

					// Center the form on the desktop
					int left = primaryScreenBounds.X + ( ( primaryScreenBounds.Width / 2 ) - ( formBounds.Width / 2 ) );
					int top = primaryScreenBounds.Y + ( ( primaryScreenBounds.Height / 2 ) - ( formBounds.Height / 2 ) );
					
					formBounds = new Rectangle( top, left, formBounds.Width, formBounds.Height );
				}

				if( FormBorderStyle == FormBorderStyle.Sizable || FormBorderStyle == FormBorderStyle.SizableToolWindow )
				{
					// Move and resize the window and save the bounds
					m_windowBounds = Bounds = formBounds;
				}
				else
				{
					// Just mvoe the window and don't change the size
					m_windowBounds = formBounds;
					Location = formBounds.Location;
				}
			}

			if( m_userData.ContainsKey( SETTINGS_STATE ) )
			{
				// If the app was closed in a min state then just restore it to a normal state.
				FormWindowState state = (FormWindowState) m_userData[ SETTINGS_STATE ];
				if( state == FormWindowState.Minimized )
				{
					state = FormWindowState.Normal;
				}

				// Set the window state
				WindowState = state;
			}
		}

		/// <summary>
		/// Saves the application and user data.
		/// </summary>
		protected void SaveData()
		{
			if( !string.IsNullOrEmpty( m_storeName ) )
			{
				WriteObjectToXmlFile( m_appDataFilename, m_appData );
				WriteObjectToXmlFile( m_userDataFilename, m_userData );
			}
		}

		/// <summary>
		/// Saves the window settings.
		/// </summary>
		private void SaveWindowSettings()
		{
			// Save the bound/position of the window.
			m_userData[ SETTINGS_BOUNDS ] = m_windowBounds;

			// Save the windows state
			m_userData[ SETTINGS_STATE ] = WindowState;
		}

		/// <summary>
		/// Writes the data object to an XML file.
		/// </summary>
		/// <param name="filename">The filename.</param>
		/// <param name="obj">The obj to serialize to the file.</param>
		private void WriteObjectToXmlFile( string filename, object obj )
		{
			string xml = ObjectSerializer.ObjectToXml( obj );
			using( StreamWriter writer = File.CreateText( filename ) )
			{
				writer.Write( xml );
			}
		}

		/// <summary>
		/// Reads the data object from an XML file.
		/// </summary>
		/// <param name="filename">The filename.</param>
		/// <returns>The deserialized object.</returns>
		private object ReadObjectFromXmlFile( string filename )
		{
			object obj = null;
			string xml = null;

			using( StreamReader reader = File.OpenText( filename ) )
			{
				xml = reader.ReadToEnd();
			}

			if( !string.IsNullOrEmpty( xml ) )
			{
				obj = ObjectSerializer.XmlToObject( xml );
			}

			return obj;
		}
	}
}
